package com.blobfish.fiction.book.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.sql.Condition;
import cn.hutool.db.sql.SqlUtil;
import cn.hutool.http.HttpUtil;
import com.blobfish.fiction.book.constant.BookMessageEnum;
import com.blobfish.fiction.book.dto.BookDto;
import com.blobfish.fiction.book.dto.BookTableDto;
import com.blobfish.fiction.book.service.IBookService;
import com.blobfish.fiction.common.base.exception.GlobalErrorException;
import com.blobfish.fiction.common.base.knowledge.DataDictKnowledge;
import com.blobfish.fiction.common.base.model.QmResultTable;
import com.blobfish.fiction.common.base.service.impl.BaseServiceImpl;
import com.blobfish.fiction.common.model.Book;
import com.blobfish.fiction.common.model.BookChapter;
import com.jfinal.kit.Kv;
import com.jfinal.kit.PropKit;
import com.jfinal.log.Log;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.plugin.redis.Redis;

import java.util.ArrayList;
import java.util.List;

/**
 * 小说的service
 *
 * @author blobfish
 */
public class BookServiceImpl extends BaseServiceImpl<Book, BookDto> implements IBookService<Book, BookDto> {
    /**
     * 搜索小说url模板
     */
    private static final String SEARCH_BOOK_URL_TMP = "https://m.wujixiaoshuo.com/SearchBook.php?keyword={}";

    private Book dao = new Book().dao();
    private BookChapter bookChapterDao = new BookChapter().dao();
    private static final Log log = Log.getLog(BookServiceImpl.class);

    /**
     * 添加书本信息
     *
     * @param book 待添加的数据
     * @return
     */
    @Override
    public synchronized void addBook(Book book) {
        if (book == null) {
            log.error("要保存的数据不存在!");
            return;
        }
        if (!isExists(book.getBookNo())) {
            book.save();
        }
    }

    /**
     * 判断书本编号是否存在
     *
     * @param bookNo
     * @return
     */
    @Override
    public Boolean isExists(String bookNo) {
        List<Book> bookList = dao.find("select * from book where book_no = ?", bookNo);
        if (bookList.size() > 0) {
            return Boolean.TRUE;
        } else {
            return Boolean.FALSE;
        }
    }

    @Override
    public Boolean isDownloaded(String bookNo) {
        boolean isDownloaded = false;
        // 检查缓存中是否存在
        isDownloaded = Redis.use("master").sismember(PropKit.get("cacheName.DOWNLOADED_BOOK_NO"), bookNo);
        if (isDownloaded) {
            return isDownloaded;
        }
        // 如果本地不存在小说，就抛出异常
        if (!isExists(bookNo)) {
            throw new GlobalErrorException(BookMessageEnum.BOOK_NAME_NOT_BLANK);
        }
        // 根据bookNo获取书本信息
        Book book = getByBookNo(bookNo);
        String bookState = book.getState();
        // 如果状态为已下载或下载中，则说明该小说为重复任务
        if (DataDictKnowledge.BookStateEnum.DOWNLOADING.getDataCode().equals(bookState) ||
                DataDictKnowledge.BookStateEnum.DOWNLOADED.getDataCode().equals(bookState)) {
            isDownloaded = true;
        }
        return isDownloaded;
    }

    @Override
    public Book getByBookNo(String bookNo) {
        if (StrUtil.isEmpty(bookNo)) {
            throw new GlobalErrorException(BookMessageEnum.BOOK_NO_NOT_BLANK);
        }
        return dao.findFirst("select * from book where book_no = ?", bookNo);
    }

    @Override
    public List<Book> listByState(String state) {
        if (StrUtil.isEmpty(state)) {
            log.error("小说状态不能为空!");
            throw new GlobalErrorException(BookMessageEnum.BOOK_STATE_NOT_BLANK);
        }
        return dao.find("select * from book where state = ?", state);
    }

    @Override
    public void dropTxtByBookNo(String bookNo) {
        String txtPath = getTxtPathByBookNo(bookNo);
        if (StrUtil.isNotBlank(txtPath)) {
            FileUtil.del(txtPath);
        }
    }

    @Override
    public Long getLastestChapterNumOfWebByBookNo(String bookNo) {
        String urlFmt = "https://m.wujixiaoshuo.com/{}/all.html";
        String htmlStr = HttpUtil.get(StrUtil.format(urlFmt, bookNo));
        List<String> resultList = ReUtil.findAll("<p><a href=\"/.*?/(.*?)\\.html\">", htmlStr, 1);
        if (CollectionUtil.isNotEmpty(resultList)) {
            String lastItem = resultList.get(resultList.size() - 1);
            return Convert.toLong(lastItem);
        }
        return 0L;
    }

    /**
     * 生成小说
     *
     * @param bookNo 小说编号
     * @return 返回生成的小说路径
     */
    public synchronized String buildBook(String bookNo) {
        try {
            String functionPath = getTxtPathByBookNo(bookNo);
            if (FileUtil.exist(functionPath)) {
                // 如果小说在本地存在，则直接返回
                return functionPath;
            } else {
                List<BookChapter> bookChapterList = bookChapterDao.find("select * from book_chapter where book_no = ? order by chapter_num_main asc, chapter_num_sub asc", bookNo);
                List<String> contentList = new ArrayList<String>(bookChapterList.size());
                bookChapterList.forEach(item -> contentList.add(item.getContent()));
                FileUtil.appendUtf8Lines(contentList, functionPath);
                return functionPath;
            }
        } catch (Exception e) {
            log.error("生成小说失败:" + e.getMessage());
            return null;
        }
    }

    /**
     * 从外网搜索
     *
     * @param qmParam
     * @return
     */
    public QmResultTable<BookDto> searchFromWeb(BookTableDto qmParam) {
        if (Validator.isEmpty(qmParam.getName())) {
            throw new GlobalErrorException(BookMessageEnum.BOOK_NAME_NOT_BLANK);
        }
        String searchUrl = StrUtil.format(SEARCH_BOOK_URL_TMP, qmParam.getName());
        String searchResultHtml = HttpUtil.get(searchUrl);
        List<String> bookList = ReUtil.findAll("<div class=\"hot_sale\">(.*?)</div>", searchResultHtml, 1);
        bookList.addAll(ReUtil.findAll("<div class=\"hot_sale hot_saleEm\">(.*?)</div>", searchResultHtml, 1));
        List<BookDto> dtoList = new ArrayList<>(bookList.size());
        if (CollectionUtil.isNotEmpty(bookList)) {
            bookList.forEach(item -> {
                String name = ReUtil.get("<p class=\"title\">(.*?)</p>", item, 1).trim();
                String bookNo = ReUtil.get("<a href=\"/(.*?)/\">", item, 1).trim();
                String author = ReUtil.get("<a href=\"/author/.*/\">(.*?)</a>", item, 1);
                dtoList.add(new BookDto(name, author, bookNo, "5"));
            });
        }
        QmResultTable<BookDto> result = new QmResultTable<>();
        result.setCount(bookList.size());
        result.setCode(0);
        result.setData(dtoList);
        return result;
    }

    /**
     * 本地搜索小说方法
     *
     * @param qmParam
     * @return
     */
    public QmResultTable<BookDto> searchFromLocal(BookTableDto qmParam) {
        Kv cond = new Kv();
        if (Validator.isNotEmpty(qmParam.getName())) {
            cond.set("name like", SqlUtil.buildLikeValue(qmParam.getName(), Condition.LikeType.Contains, false));
        }
        Page<Record> resultPage = Db.paginate(qmParam.getPage(), qmParam.getLimit(), Db.getSqlPara("book.listBook", Kv.by("cond", cond)));
        return renderTableResult(resultPage, BookDto.class);
    }

    /**
     * 新增小说，如果本地已经存在该小说，则直接返回小说编号
     *
     * @param book
     * @return 返回小说编号
     */
    public String insert(Book book) {
        String bookNo = book.getBookNo();
        if (!isExists(bookNo)) {
            synchronized (this) {
                if (!isExists(bookNo)) {
                    book.save();
                }
            }
        }
        return book.getBookNo();
    }

    /**
     * 根据小说编号获取对应txt文件路劲
     * @param bookNo
     * @return
     */
    private String getTxtPathByBookNo(String bookNo) {
        String functionPathTmp = "{}{}.txt";
        Book book = dao.findFirst("select * from book where book_no = ?", bookNo);
        if (book == null) {
            return "";
        }
        String bookName = book.getName();
        return StrUtil.format(functionPathTmp, PropKit.get("saveFunctionPath"), bookName);
    }
}
